########################################################################
#  Wiizard - A Wii games manager
#  Copyright (C) 2023  CYBERDEViL
#
#  This file is part of Wiizard.
#
#  Wiizard is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  Wiizard is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
########################################################################


from PyQt5.QtWidgets import (
  QDialog,
  QVBoxLayout,
  QLabel,
  QListWidget,
  QPushButton,
  QTextEdit,
  QProgressBar
)
from PyQt5.QtGui import QBrush, QColor
from PyQt5.QtCore import Qt, QVariant

from wiizard.operations import (
  GameOperationsThread,
  GameOperation,
  GAME_OP_DELETE,
  GAME_OP_MOVE,
  GAME_OP_ADD
)
from wiizard.translations import _


class ApplyChangedDialog(QDialog):
  def __init__(self, sourceModels, parent=None):
    QDialog.__init__(self, parent=parent)

    """
    There are {} operations pending.
     - {} are new games
     - {} are games marked for deletion

    Order of execution:
     - delete x from y
     - delete x from y
     
    """

    self.__sourceModels = sourceModels
    self.operations     = []
    self.thread         = None

    self.setWindowTitle(_("Apply changes"))
    self.setMinimumWidth(400)

    """
        PARTITION A      |    PARTITION B
    GAME_1  4GB          | GAME_3  4GB
    GAME_2  4GB          |
                         |
    free: 0GB used: 8GB  | free: 0GB used: 4GB

    If we where to swap PARTITION A with PARTITION B the operations list should
    look as follows:
     - ADD GAME_1 to   PARTITION B
     - DEL GAME_1 from PARTITION A
     - ADD GAME_3 to   PARTITION A
     - DEL GAME_3 from PARTITION B
     - ADD GAME_2 to   PARTITION B
     - DEL GAME_2 from PARTITION A
    """

    # Create operations list
    # Stage 1: Delete games that are marked for deletion and don't have a
    #          add operation pending.
    # Stage 2: Add games that need to be added but are marked for deletion.
    #          - After adding a game, delete it from the other partition.

    # 1. We need a list of games that we can delete without consequences.
    # 2. We need a list of games that have conflicts and sort those.

    # Statistics gathering
    toDelete    = []
    toAdd       = []
    toMove      = []
    for sourceModel in sourceModels:
      toDelete += sourceModel.pendingDelGames
      toAdd    += sourceModel.pendingAddGames
    for game in list(toDelete):
      if game in toAdd:
        toDelete.remove(game)
        toAdd.remove(game)
        toMove.append(game)

    # Sort sourceModels so we start with the one with the lesser free space.
    sourceModels = sorted(sourceModels, key=lambda m: (m.getFree()))

    # Stage 1: Add games to the operation list that we can delete right a way
    #          (these games do not also have a pending copy operation).
    for sourceModel in sourceModels:
      for game in sourceModel.pendingDelGames:
        if game in toDelete:
          self.operations.append(GameOperation(game, sourceModel, GAME_OP_DELETE))

    # Stage 2: Move games (First copy and then delete)
    for sourceModel in sourceModels:
      for game in sourceModel.pendingAddGames:
        if game in toMove:
          self.operations.append(GameOperation(game, sourceModel, GAME_OP_MOVE))

    # Stage 3: Add remaining games (Copy)
    for sourceModel in sourceModels:
      for game in sourceModel.pendingAddGames:
        if game in toAdd:
          self.operations.append(GameOperation(game, sourceModel, GAME_OP_ADD))

    totalOperations = len(self.operations)
    self.operationsLeft = totalOperations

    layout = QVBoxLayout(self)

    self.pendingLabel = QLabel(self)
    self.pendingLabel.setWordWrap(True)
    self.__reGenPendingLabel()

    self.operationsList = QListWidget(self)

    # TODO add icons
    for operation in self.operations:
      if operation.operation == GAME_OP_DELETE:
        self.operationsList.addItem(_("Delete \"{}\" from \"{}\"")
                                    .format(operation.game.title,
                                            operation.target.location))
      elif operation.operation == GAME_OP_ADD:
        self.operationsList.addItem(_("Add \"{}\" to \"{}\" from \"{}\"")
                                    .format(operation.game.title,
                                            operation.target.location,
                                            operation.game.sourceModel.location))
      elif operation.operation == GAME_OP_MOVE:
        self.operationsList.addItem(_("Move \"{}\" to \"{}\" from \"{}\"")
                                    .format(operation.game.title,
                                            operation.target.location,
                                            operation.game.sourceModel.location))

    self.logWidget = QTextEdit(self)
    self.logWidget.hide()

    self.applyButton  = QPushButton(_("Apply"), self)
    self.cancelButton = QPushButton(_("Cancel"), self)
    self.cancelButton.hide()
    self.doneButton   = QPushButton(_("Done"), self)
    self.doneButton.hide()

    self.progressBar = QProgressBar(self)
    self.progressBar.setFormat(("Busy please wait.. %v / %m %p%"))
    self.progressBar.setMaximum(totalOperations)
    self.progressBar.setValue(0)
    self.progressBar.hide()

    layout.addWidget(self.pendingLabel)
    layout.addWidget(self.operationsList)
    layout.addWidget(self.logWidget)
    layout.addWidget(self.progressBar)
    layout.addWidget(self.applyButton)
    layout.addWidget(self.cancelButton)
    layout.addWidget(self.doneButton)

    self.applyButton.clicked.connect(self.onApply)
    self.cancelButton.clicked.connect(self.onCancel)
    self.doneButton.clicked.connect(self.onDoneClicked)
    self.operationsList.currentRowChanged.connect(self.__setLogForOperation)

  def closeEvent(self, event):
    if self.thread is not None:
      self.onCancel()
      event.ignore()
      return
    QDialog.closeEvent(self, event)

  def onApply(self):
    self.logWidget.show()
    self.applyButton.hide()
    self.cancelButton.show()
    self.progressBar.show()

    self.thread = GameOperationsThread(self.operations)

    self.thread.progress.connect(self.__onProgressUpdate)
    self.thread.completed.connect(self.__onComplete)

    self.thread.start()

  def onCancel(self):
    self.progressBar.setFormat(_("Cancelling please wait on current "
                               "operation.. %v / %m %p%"))
    self.thread.cancel()

  def onDoneClicked(self):
    self.accept()

  def __reGenPendingLabel(self):
    if self.operationsLeft == 1:
      self.pendingLabel.setText(_("There is 1 operation pending"))
    else:
      self.pendingLabel.setText(_("There are {} operations pending")
                                .format(self.operationsLeft))

  def __onProgressUpdate(self, progress, errorMessage):
    self.progressBar.setValue(progress)
    index = progress - 1
    # https://doc.qt.io/qt-5/qlistwidgetitem.html
    item = self.operationsList.item(index)
    if errorMessage:
      item.setBackground(QBrush(QColor("#9C0000"),  Qt.SolidPattern))
      item.setData(Qt.UserRole, QVariant(errorMessage))
      #txt = self.logWidget.toPlainText() + "\n" + errorMessage
      #self.logWidget.setPlainText(txt)
    else:
      item.setBackground(QBrush(QColor("#009C00"),  Qt.SolidPattern))
      item.setData(Qt.UserRole, QVariant("OK"))

    self.operationsList.setCurrentRow(index)
    self.__setLogForOperation(index)
    self.operationsLeft -= 1
    self.__reGenPendingLabel()

  def __onComplete(self):
    self.cancelButton.hide()
    self.thread.wait()
    self.thread.quit()
    self.thread = None
    self.doneButton.show()
    self.progressBar.setFormat(_("Done %v / %m %p%"))

    for sourceModel in self.__sourceModels:
      sourceModel.startScan()

  def __setLogForOperation(self, index):
    item    = self.operationsList.item(index)
    message = item.data(Qt.UserRole)
    self.logWidget.setPlainText(str(message))
